<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>端口转发工具</title>
  <link href="http://unpkg.com/layui@2.9.20/dist/css/layui.css" rel="stylesheet">
  <!-- <link href="./layui@2.9.20.css" rel="stylesheet"> -->
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
  <div class="layui-header" >
    <div class="layui-logo"><img src="./favicon.ico" class="layui-nav-img">端口映射管理</div>
    <!-- 头部区域（可配合layui已有的水平导航） -->
    <!-- <ul class="layui-nav layui-layout-left">
      <li class="layui-nav-item"><a href="">header-nav-1</a></li>
      <li class="layui-nav-item"><a href="">商品管理</a></li>
      <li class="layui-nav-item"><a href="">用户</a></li>
    </ul> -->
    <ul class="layui-nav layui-layout-right">
      <li class="layui-nav-item"><a href="#" onclick="location.reload();">刷新</a></li>
        <li class="layui-nav-item">
        <a id="id-username" href="javascript:;">
          <!-- <img src="" class="layui-nav-img"> -->
          undefined
        </a>
        <dl class="layui-nav-child">
          <dd><a href="javascript:;">修改密码</a></dd>
          <dd><a href="javascript:window.location.href = 'login.html';">退出</a></dd>
        </dl>
      </li>
    </ul>
  </div>
   
  <!-- 左侧导航区域（可配合layui已有的垂直导航） -->
  <div class="layui-side layui-bg-black">
    <div class="layui-side-scroll">
      <ul class="layui-nav layui-nav-tree" lay-filter="filter-nav">
        <li class="layui-nav-item" >
            <a class="layui-this" href="javascript:;" id="nav-device">在线设备</a>
            <!-- <dl class="layui-nav-child">
              <dd><a href="javascript:;">列表一</a></dd>
              <dd><a href="javascript:;">列表二</a></dd>
            </dl> -->
          </li>
        <li class="layui-nav-item" >
          <a  href="javascript:;" id="nav-index">转发规则</a>
          <!-- <dl class="layui-nav-child">
            <dd><a href="javascript:;">列表一</a></dd>
            <dd><a href="javascript:;">列表二</a></dd>
          </dl> -->
        </li>

        <li class="layui-nav-item" >
          <a href="javascript:;" id="nav-monitor">监控/统计</a>
          <!-- <dl class="layui-nav-child">
            <dd><a href="javascript:;">列表一</a></dd>
            <dd><a href="javascript:;">列表二</a></dd>
          </dl> -->
        </li>
      </ul>
    </div>
  </div>
  <div id="content-container">
    <div class="layui-body"> <h1> loading ...</h1> </div>
  </div> 
  <div class="layui-footer">
    <!-- 底部固定区域 -->
    ©copyright 2024 changxunzhou@qq.com 
    [<span style="background-color: #16b777;" class="layui-text" id="pywebview-status"></span>]
  </div>
    <!-- 内容区模板：主页 -->
  <script type="text/html" id="body-index">
    <div class="layui-body">
      <blockquote class="layui-elem-quote layui-text">
        端口映射列表
      </blockquote>
      <table class="layui-hide" id="ID-rule-table" lay-filter="test"></table>
    </div>
  </script>
  <!-- 内容区模板：设备列表 -->
  <script type="text/html" id="body-device">
    <div class="layui-body">
      <blockquote class="layui-elem-quote layui-text">
        在线设备列表
      </blockquote>
      <table class="layui-hide" id="id-table-device" lay-filter="device"></table>
    </div>
  </script>
    <!-- 行操作单元格模板 -->
    <script type="text/html" id="id-table-device-row-action" lay-filter="device">
      <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="upgrade">升级</a>
    </script>
  <!-- 内容区模板：监控页 -->
  <script type="text/html" id="body-monitor">
  <div class="layui-body">
    <blockquote class="layui-elem-quote layui-text">
      监控/统计
    </blockquote>
    <hr class="layui-border-orange">
    <div class="layui-row layui-bg-gray">
      <div class="layui-col-md3 layui-col-md-offset1">
        <div class="grid-demo grid-demo-bg1">
          <button class="layui-btn layui-btn-danger">
            规则数
            <span class="layui-badge layui-bg-gray" id="stats-rule-cnt">0</span>
          </button>
        </div>
      </div>
      <div class="layui-col-md3">
        <div class="grid-demo">
          <button class="layui-btn">
            会话数
            <span class="layui-badge layui-bg-gray" id="stats-session-cnt">0</span>
          </button>
        </div>
      </div>
      <div class="layui-col-md3">
        <div class="grid-demo">
          <button class="layui-btn layui-bg-purple">
            连接数
            <span class="layui-badge layui-bg-gray" id="stats-conn-cnt">0</span>
          </button>
        </div>
      </div>
    </div>
    <hr class="layui-border-green">
    <blockquote class="layui-elem-quote layui-text">
      当前会话列表
    </blockquote>
    <table class="layui-hide" id="table-monitor" lay-filter="monitor"></table>
  </div>
  </script>
  <!-- 工具栏模板 -->
  <script type="text/html" id="ID-toolbar" lay-filter="test">
    <div class="layui-inline" lay-event="add"><i class="layui-icon layui-icon-add-1"></i></div>
    <div class="layui-inline" lay-event="del"><i class="layui-icon layui-icon-delete"></i></div>
    <div class="layui-inline" lay-event="fresh"><i class="layui-icon layui-icon-refresh"></i></div>
  </script>
  <!-- 行操作单元格模板 -->
  <script type="text/html" id="ID-demo-table-tool" lay-filter="test">
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
  </script>
</div>
<script src="http://unpkg.com/layui@2.9.20/dist/layui.js"></script>
<!-- <script src="./layui@2.9.20.js"></script> -->
<script>
//JavaScript代码区域
layui.use('layer', function(){
  // 获得需要的内置组件
  var layer = layui.layer; // 弹层
  var laypage = layui.laypage; // 分页
  var table = layui.table; // 表格
  var form = layui.form;
  var element = layui.element; // 元素操作
  // layer.msg('everything is ready');
  function log(msg) {
    var container = document.getElementById('pywebview-status');
    let formatter = new Intl.DateTimeFormat('en-US', {
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
    });
    let formattedTime = formatter.format(new Date());
    container.innerHTML = formattedTime + ' ' + msg;
  }
  // 导航点击事件
  element.on('nav(filter-nav)', function(elem){
      // elem: 当前点击的元素 jQuery 对象
      // layer.msg(this.id +' clicked');
      let pageId = 'body-' + this.id.split('-')[1];
      log('nav to ' + elem.text());
      renderContent(pageId);
  });
  
  function pageIndex_init(){
    // table 实例处理
    var mytable = table.render({
      elem: '#ID-rule-table',
      height: 420,
      url: '', // 数据接口（此处为静态数据）
      title: '规则表',
      page: false, // 开启分页
      toolbar: '#ID-toolbar', // 开启工具栏，此处显示默认图标，可以自定义模板，详见文档
      totalRow: true, // 开启合计行
      cols: [[ // 表头
        {type: 'checkbox', fixed: 'left'},
        {field: 'id', title: 'ID', width:80, sort: true, fixed: 'left', totalRow: '合计 : '},
        {field: 'listen', title: 'Listen', width:150},
        {field: 'agent_id', title: 'AgentID', width:150, sort: true},
        {field: 'target', title: 'Target', width:200},
        {field: 'status', title: 'Status', width:100, templet: '<div><span class="layui-badge layui-bg-{{= d.color}}">{{= d.status }}</span></div>'},
        {fixed: 'right', title: '操作', width: 150, align:'center', toolbar: '#ID-demo-table-tool'}
      ]],
      data: [],
    });

    // 行操作按钮事件
    table.on('tool(test)', function(obj){
      log('row event: ' + obj.event);
      console.log('row click del: ', obj.data);
      var listen = obj.data.listen;
      // layer.alert(layui.util.escape(reqData));
      layer.confirm('确定删除监听 '+listen+' 吗？', {icon: 3}, function(){
          layer.closeAll();
          log('DELETE /api/rule_del');
          fetch('/api/rules?listen='+listen, {
              method: "DELETE",
              headers: {"Accept": "application/json"}
          }).then(async res => {
            tableFresh();
            if (res.status == 'success') {
              tableFresh();
              layer.msg("删除成功", {icon: 1});
            } else {
              layer.msg('删除失败：' + res.data, {icon: 2});
            }
          })
        });
      
    });
    // 表头自定义工具栏事件
    table.on('toolbar(test)', function(obj){
      var options = obj.config; // 获取当前表格基础属性配置项
      var layEvent = obj.event; // 获得自定义元素对应的 lay-event 属性值
      log('toolbar click: ' + obj.event); 
      var checkStatus = table.checkStatus(obj.config.id);
      switch(obj.event){
        case 'fresh':
          tableFresh();
          layer.msg('刷新列表', {icon: 1});
          break;
        // 批量删除
        case 'del':
          var data = checkStatus.data;
          if (data.length == 0) {
            layer.msg('请先勾选要删除的数据', {icon: 2});
            return;
          }
          // layer.alert(layui.util.escape(JSON.stringify(data)));
          layer.msg('暂不支持批量删除', {icon: 2});
          break;
        case 'add':
          layer.open({
            title: '添加规则',
            type: 1,
            area: ['500px', '350px'],
            content: `
            <div class="layui-form" lay-filter="filter-test-layer" style="margin: 16px;">
              <div class="demo-login-container">
                <div class="layui-form-item">
                  <div class="layui-input-group">
                    <div class="layui-input-split layui-input-prefix">
                      监听端口
                    </div>
                    <input type="number" name="local_port" value="" lay-verify="required" placeholder="port" lay-reqtext="请填写port" autocomplete="off" class="layui-input">
                  </div>
                </div>
                <div class="layui-form-item">
                  <div class="layui-input-group">
                    <div class="layui-input-split layui-input-prefix">
                      设备ID
                    </div>
                    <input type="text" name="device" value="" lay-verify="required" placeholder="device ID" lay-reqtext="请填写设备ID" autocomplete="off" class="layui-input" lay-affix="clear">
                  </div>
                </div>
                <div class="layui-form-item">
                  <div class="layui-input-group">
                    <div class="layui-input-split layui-input-prefix">
                      远端  IP
                    </div>
                    <input type="text" name="target_ip" value="" lay-verify="required" placeholder="remote ip" lay-reqtext="请填写remote ip" autocomplete="off" class="layui-input">
                  </div>
                </div>
                <div class="layui-form-item">
                  <div class="layui-input-group">
                    <div class="layui-input-split layui-input-prefix">
                      远端端口
                    </div>
                    <input type="number" name="target_port" value="" lay-verify="required" placeholder="remote port" lay-reqtext="请填写remote port" autocomplete="off" class="layui-input">
                  </div>
                </div>
                <div class="layui-form-item">
                  <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">添加</button>
                </div>

              </div>
            </div>
            `,
            success: function(data){
              // 对弹层中的表单进行初始化渲染
              form.render();
              // 表单提交事件
              form.on('submit(demo-login)', function(data){
                  layer.closeAll();
                  var field = data.field; // 获取表单字段值
                //   var rule = field.local_port+','+field.device+','+field.target_ip+':'+field.target_port;
                //   console.log('add rule: ', rule);
                  fetch('/api/rules', {
                      method: "POST",
                      headers: {"Content-Type": "application/json"},
                      body: JSON.stringify({
                          listen: '0.0.0.0:' + field.local_port,
                          agent_id: field.device,
                          target: field.target_ip+':'+field.target_port
                      })
                  }).then(async res => {
                    console.log('add result: ', res);
                    if (res.status == 'success') {
                      tableFresh();
                      layer.msg("添加成功", {icon: 1});
                    } else {
                      layer.msg('添加失败：' + res.data, {icon: 2});
                    }
                });
                return false; // 阻止默认 form 跳转
              });
            }
          });
          break;
      }
    });
 
    function tableFresh() {
      log('GET /api/rules');
      fetch('/api/rules', {
          method: "GET",
          headers: {"Accept": "application/json"},
      }).then(async function(response) {
        console.log('response:', response);
        if (response.status == 'success') {
          // 请求成功，处理返回的数据
          console.log('fresh table:', response);
          var tableData = [];
          var idx = 1;
          for (item of response.data) {
            tableData.push({
              id: idx++,
              listen: item.listen,
              agent_id: item.agent_id,
              target: item.target,
              status: item.status,
              color: (item.status == 'online') ? 'green' : 'red'
            });
          }
          mytable.reload({data: tableData, limit: tableData.length});
        } else {
            // 请求失败，抛出错误
            console.log("请求失败:" + response);
        }
      });
    }
    // page init code
    tableFresh();
  }

  window.addEventListener('pywebviewready', function() {
    log('pywebview is ready');
    // from now on you can pywebview.api.py_func to call python functions
    // tableFresh();
  });
  function pageMonitor_init(){
    var mytable = table.render({
      elem: '#table-monitor'
      ,height: 'full-100'
      ,url: '/api/sessions'
      ,page: false
      ,cols: [[
        {field:'sid', width:100, title: 'SID'}
        ,{field:'duration', width:150, title: 'duration(s)', sort: true}
        ,{field:'incoming', width:300, title: '请求侧'}
        ,{field:'forwad_to', width:300, title: '转发侧'}
        ,{field:'rx_tx', width:150, title: '流量(rx/tx)'}
      ]],
      parseData: function(res){
        console.log('parseData: ', res);
        let data = []
        for (item of res.data) {
          data.push({
            sid: item.sid,
            duration: item.duration,
            incoming: item.client_conn,
            rx_tx: (item.bytes_rx/1000.0).toFixed(2) + 'KB/' + (item.bytes_tx/1000.0).toFixed(2) + 'KB',
            forwad_to: item.target_conn,
          });
        }
        return {
          "code": 0, // 解析接口状态
          "msg": "", // 解析提示文本
          "count": 0, // 解析数据长度
          "data": data 
        };
      },
    });

    function getStats(){
      fetch('/api/stats', {
        method: "GET",
        headers: {"Accept": "application/json"},
      })
      .then(async function(response) {
        if (response.ok) {
          // 请求成功，处理返回的数据
          let data = await response.json();
          console.log('stats:', data);
          var ele1 = document.getElementById('stats-rule-cnt');
          ele1.innerHTML = data.rules;
          var ele2 = document.getElementById('stats-session-cnt');
          ele2.innerHTML = data.sessions;
          var ele2 = document.getElementById('stats-conn-cnt');
          ele2.innerHTML = data.connections;
        } else {
          // 请求失败，抛出错误
          console.log("请求失败，状态码：" + response.status);
        }
      });
    }
    // page init code
    // getStats();
  }
  function pageDevice_init(){
    var mytable = table.render({
      elem: '#id-table-device'
      ,height: 'full-100'
      ,url: '/api/agents'
      ,page: false
      ,cols: [[
        {field:'id', width:150, title: 'Device ID'}
        ,{field:'start_time', width:200, title: 'online at', sort: true}
        ,{field:'from', width:150, title: 'from'}
        ,{field:'version', width:100, title: 'version'}
        ,{field:'total_session', width:100, title: 'sessions'}
        ,{fixed: 'right', title: '操作', width: 150, align:'center', toolbar: '#id-table-device-row-action'}
      ]],
      parseData: function(res){
        console.log('parseData: ', res);
        let data = []
        for (item of res.data) {
          data.push({
            id: item.agent_id,
            start_time: item.start_time,
            from: item.addr,
            version: item.version,
            total_session: item.total_session,
          });
        }
        return {
          "code": 0, // 解析接口状态
          "msg": "", // 解析提示文本
          "count": 0, // 解析数据长度
          "data": data 
        };
      },
    });
    table.on('tool(device)', function(obj){
      log('row event: ' + obj.event);
      console.log('device row click : ', obj.data);
      layer.msg("暂不支持远程升级"+obj.data.id);
    });
    // init code
  }
  function renderContent(contentId='body-device') {
    var contentEle = document.getElementById(contentId);
    if (contentEle) {
        var contentContainer = document.getElementById('content-container');
        contentContainer.innerHTML = contentEle.innerHTML;
    }
    if (contentId == 'body-index') {
      pageIndex_init();
    }
    if (contentId == 'body-monitor') {
      pageMonitor_init();
    }
    if (contentId == 'body-device') {
      pageDevice_init();
    }
  }

  // init code
  fetch('/api/userinfo', { method: 'GET' })
    .then(res => {
          console.log('userinfo:', res.data);
          document.getElementById('id-username').innerHTML = res.data.username;
    })
  // 1. render index page body (default)
  renderContent();
});

// 保存原始的open方法
const originalOpen = XMLHttpRequest.prototype.open;
// 重写open方法
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
    console.log(`[diy-xhr] ${method} ${url}`);
    // 调用原始的 open 方法
    originalOpen.call(this, method, url, async, user, password);

    console.log('[diy-xhr] add token: ', localStorage.getItem('token'));
    const token = localStorage.getItem('token');
    if (!token){
      window.location.href = 'login.html';
    } else {
      this.setRequestHeader('Token', `${token}`);
    }
    // 在请求发送前添加 token 到请求头
    // this.addEventListener('readystatechange', function () {
    //   console.log('[diy-xhr] readyState: ', this.readyState);
    //   if (this.readyState === 1) {
    //     console.log('[diy-xhr] add token: ', localStorage.getItem('token'));
    //     const token = localStorage.getItem('token');
    //     if (!token){
    //       window.location.href = 'login.html';
    //     } else {
    //       this.setRequestHeader('Token', `${token}`);
    //     }
    //   }
    // });
};

// fetch 封装拦截器
const originalFetch = window.fetch;
// 创建一个自定义的fetch拦截器函数
window.fetch = function (url, options = {}) {
    console.log(`[diy-fetch] ${options.method || 'GET'} ${url}`);
    const token = localStorage.getItem('token');
    if (!token) {
      window.location.href = 'login.html';
    }
    options.headers = {
       ...options.headers,
        'Token': `${token}`
    };

    return new Promise((resolve, reject) => {
      originalFetch(url, options)
           .then(response => {
                if (response.status === 401) {
                    // 如果状态码是401，跳转到login.html
                    window.location.href = 'login.html';
                    reject(new Error('Unauthorized'));
                } else {
                    // 否则正常返回响应，并解析为JSON（可根据需要修改）
                    response.json().then(data => {
                        resolve(data);
                    }).catch(err => {
                        reject(err);
                    });
                }
            })
           .catch(error => {
                // 处理错误
                console.error('Fetch error:', error);
                reject(error);
            });
    });
}

// fetch example:
// fetch('https://example.com/api', { method: 'GET' })
//    .then(data => {
//         console.log('Response data:', data);
//     })
//    .catch(error => {
//         console.error('Request error:', error);
//     });
// xhr example:
// const xhr = new XMLHttpRequest();
// xhr.open('GET', 'https://example.com/api', true)
//    .then(data => {
//         console.log('Response data:', data);
//     })
//    .catch(error => {
//         console.error('Request error:', error);
//     });
</script>
</body>
</html>